
#ifndef META_STREAM_HPP
#define META_STREAM_HPP
#include <type_traits>
#include "stream_timer_condition.hpp"
#include "..\meta_object\meta_objects.hpp"
#include "..\meta_object\meta_looper.hpp"
#include "..\exp_utilities\exp_utilities.hpp"
#include "meta_iostream\meta_basic_iostream.hpp"

namespace meta_ios {

    using meta_objects::meta_object;
    using meta_objects::meta_ret_object;
    using meta_objects::meta_timer_object;
    using meta_objects::meta_empty_o;
    using meta_objects::meta_timer_object_details::meta_always_continue;
    using meta_objects::invoke_object_if;
    using meta_loop::meta_looper;
    using meta_loop::meta_looper_t;
    using namespace exp_utilities;

    namespace io_stream_transform_details
    {
        namespace io_stream_traits {
            template<class T> struct is_meta_object : std::false_type {};

            template<class OBJ, class F>
            struct is_meta_object<meta_object<OBJ, F>> : std::true_type {};
            template<class OBJ, class F, class Ret>
            struct is_meta_object<meta_ret_object<OBJ, F, Ret>> : std::true_type {};

            template<class T> constexpr bool is_meta_object_v = is_meta_object<T>::value;

            template<class T> struct is_meta_object_ret : std::false_type {};

            template<class OBJ, class F, class Ret>
            struct is_meta_object_ret<meta_ret_object<OBJ, F, Ret>> : std::true_type {};
            template<class T> constexpr bool is_meta_object_ret_v = is_meta_object_ret<T>::value;


            template<class T>
            concept meta_istream_t = is_meta_object_ret_v<T>;

            template<class T>
            concept meta_ostream_t = is_meta_object_v<T>;

        }


        template<io_stream_traits::meta_ostream_t To, io_stream_traits::meta_istream_t From> struct meta_stream
        {
            using from = From;
            using to = To;
            using cache = typename From::ret;
            using update = meta_stream<
                //invoke ostream with read in from istream
                meta_invoke<invoke_object_if<!length_equal<typename From::type, 0>>, To, From>,

                //update the istream
                meta_invoke<invoke_if<!length_equal<typename From::type, 0>>, From>
            >;
        };

    
        struct meta_stream_f
        {
            template<class mo_stream, class...>
            using apply = typename mo_stream::update;
        };
    }


    template<std::size_t Transfer_Length, 
        io_stream_transform_details::io_stream_traits::meta_ostream_t To, 
        io_stream_transform_details::io_stream_traits::meta_istream_t From, 
        class break_f = meta_always_continue>
    using meta_stream_o = meta_timer_object<
        Transfer_Length,
        io_stream_transform_details::meta_stream<To, From>,
        io_stream_transform_details::meta_stream_f,
        break_f
    >;


    template<io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f = meta_always_continue>
    using meta_all_transfer_o = meta_stream_o<exp_size<typename From::type>, To, From, break_f>;


    template<io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f = meta_always_continue>
    using meta_all_transfer = meta_looper_t<
        io_stream_transform_details::meta_timer_cond_o,
        meta_all_transfer_o<To, From, break_f>,
        meta_empty_o
    >;

    template<std::size_t N, 
        io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f = meta_always_continue 
    >
    using transfer = meta_looper_t<
        io_stream_transform_details::meta_timer_cond_o,
        meta_stream_o<N, To, From, break_f>,
        meta_empty_o
    >;

    template<
        io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f
    >
    using transfer_until = meta_looper_t<
        io_stream_transform_details::meta_timer_cond_o,
        meta_stream_o<0 - 1, To, From, break_f>,
        meta_empty_o
    >;
  
    template<class stream_t>
    using transfer_stream = meta_looper<
        io_stream_transform_details::meta_timer_cond_o, stream_t, meta_empty_o>;

    template<
        io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f = meta_always_continue
    >
    using meta_for = meta_invoke<transfer_stream<
        meta_all_transfer_o<To, From, break_f>
    >>;

    template<
        std::size_t times,
        io_stream_transform_details::io_stream_traits::meta_ostream_t To,
        io_stream_transform_details::io_stream_traits::meta_istream_t From,
        class break_f = meta_always_continue
    >
    using meta_while = meta_invoke<transfer_stream<
        meta_stream_o<times, To, From, break_f>
        >>;

    //common basic io streams
    template<class type_list>
    using meta_istream = io_stream_transform_details::meta_basic_istream_detail::meta_basic_istream<type_list>;

    template<class ...Tys>
    using meta_istream_list = meta_istream<exp_list<Tys...>>;

    template<class type_list>
    using meta_ostream = io_stream_transform_details::meta_basic_ostream_detail::meta_basic_ostream<type_list>;

    template<class type_list, class meta_function_type>
    using meta_transform_istream = io_stream_transform_details::meta_transform_istream_detail::meta_basic_transform_istream<type_list, meta_function_type>;

    template<class type_list, class meta_function_type>
    using meta_transform_ostream = io_stream_transform_details::meta_transform_ostream_detail::meta_basic_transform_ostream<type_list, meta_function_type>;

    template<class type_list, class meta_function_type>
    using meta_filter_ostream = io_stream_transform_details::meta_filter_ostream_detail::filter_ostream<type_list, meta_function_type>;

    template<std::size_t start>
    using meta_index_istream = io_stream_transform_details::meta_index_istream_detail::index_istream<start>;

    template<std::size_t start, std::size_t count>
    using meta_count = typename transfer<count, meta_ostream<exp_list<>>, meta_index_istream<start>>::to::type;

    template<std::size_t start, std::size_t count>
    using meta_count_istream = meta_istream<meta_count<start, count>>;

    using meta_getch = io_stream_transform_details::meta_replace_able_ostream_detail::replace_able_ostream;

    template<class T>
    using meta_repeat_istream = io_stream_transform_details::meta_self_repeat_ostream_detail::self_ostream<T>;

    template<class TL>
    using meta_jostream = io_stream_transform_details::meta_join_ostream_detail::join_ostream<TL>;

    template<static_str str>
    using meta_char_istream = meta_istream<
        typename transfer<
        exp_size<str_to_list<str>> -1,
        meta_ostream<exp_list<>>,
        meta_istream<str_to_list<str>>
        >::to::type
    >;



    namespace protocols {
        template<class meta_function_type>
        using only_this = io_stream_transform_details::transfer_protocols::details::this_policy_type<meta_function_type>;

        template<class meta_function_type>
        using only_arg = io_stream_transform_details::transfer_protocols::details::arg_policy_type<meta_function_type>;

        template<class meta_function_type>
        using stream_to_unref = io_stream_transform_details::transfer_protocols::details::stream_to_policy_type<meta_function_type>;

        template<class meta_function_type>
        using only_stream_to_unref = io_stream_transform_details::transfer_protocols::details::stream_to_this_policy_type<meta_function_type>;

        template<class meta_function_type>
        using only_stream_from_unref = io_stream_transform_details::transfer_protocols::details::stream_from_only_policy_type<meta_function_type>;

        template<class meta_function_type>
        using stream_cache_unref = io_stream_transform_details::transfer_protocols::details::stream_cache_policy_type<meta_function_type>;

        template<class meta_function_type>
        using only_stream_cache_unref = io_stream_transform_details::transfer_protocols::details::stream_cache_this_policy_type<meta_function_type>;

        template<class meta_stream_type>
        using stream_to_t = typename meta_stream_type::to::type;

        template<class meta_stream_type>
        using stream_from_t = typename meta_stream_type::from::type;

        template<class meta_stream_type>
        using stream_cache_t = typename meta_stream_type::cache;
    }

    template<template<class> class ...protocols>
    auto protocol_call(auto&& f) {
        return[&f]<class in_stream_t, class ...Args>(in_stream_t, Args&&...args) {
            std::invoke(f, meta_fold<in_stream_t, protocols...>{}, std::forward<Args>(args)...);
        };
    }

    template<class F, class ...protocols>
    auto protocol_call(F&& f) {
        return protocol_call<protocols::template apply...>(std::forward<F>(f));
    }
}
#endif // !META_STREAM_HPP
